package com.squeekeek.util.qrcode.Core;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfDouble;
import org.opencv.imgproc.Imgproc;

public class QRBinarize {
	public static final int PIXEL_WHITE = 0xFF;
	public static final int PIXEL_BLACK = 0x00;
	
	/**
	 * <p>
	 * Image binarization using Ohtsu's method outlined in "A Threshold Selection
	 * Method from Gray-Level histograms" in 1979.
	 * </p>
	 * 
	 * @param inImg Image matrix to binarize
	 * @return Binarized image in an 8-bit unsigned matrix
	 */
	public static Mat ohtsu(Mat inImg) {
		Mat outImg = new Mat(inImg.size(), CvType.CV_8UC1);
		
		Imgproc.threshold(inImg, outImg, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
		
		return outImg;
	}
	
	/**
	 * <p>
	 * Image binarization using Bernsen's method outlined in "Dynamic Thresholding of
	 * Grey-Level Images" in 1986.
	 * </p>
	 * 
	 * @param img Image matrix to binarize
	 * @return Binarized image in an 8-bit unsigned matrix
	 */
	public static Mat bernsen(Mat inImg) {
		int rowStart, rowEnd;
		int colStart, colEnd;
		double centerVal;
		double localContrast;
		
		Mat imgWin;
		Mat outImg;
		Core.MinMaxLocResult localMinMax;
		
		outImg = new Mat(inImg.size(), CvType.CV_8UC1);

		if(inImg != null) {
			for(int i = 0; i < inImg.rows(); i++) {
				for(int j = 0; j < inImg.cols(); j++) {
					rowStart = i - 15;
					rowEnd = i + 15;
					colStart = j - 15;
					colEnd = j + 15;
					
					// Truncate window if it goes beyond the image
					if(rowStart < 0) {
						rowStart = 0;
					}
					
					if(rowEnd >= inImg.rows()) {
						rowEnd = inImg.rows() - 1;
					}
					
					if(colStart < 0) {
						colStart = 0;
					}
					
					if(colEnd >= inImg.cols()) {
						colEnd = inImg.cols() - 1;
					}
					
					// Get window. The default size is 31
					imgWin = inImg.submat(rowStart, rowEnd, colStart, colEnd);
					centerVal = inImg.get(i, j)[0];
					
					// Get min and max pixel values
					localMinMax = Core.minMaxLoc(imgWin);
					
					// Check if the difference is sufficient
					if((int) (10 * (localMinMax.maxVal - localMinMax.minVal)) >= 15) {
						// Mixture of foreground and background pixels
						localContrast = 0.5 * (localMinMax.maxVal + localMinMax.minVal);
						
						if((int) (10 * (centerVal - localContrast)) < 0) {
							outImg.put(i, j, PIXEL_BLACK);
						}
						else {
							outImg.put(i, j, PIXEL_WHITE);
						}
					}
					else {
						// Window contains either all foreground or all background pixels
						if((int) centerVal < 128) {
							outImg.put(i, j, PIXEL_BLACK);
						}
						else {
							outImg.put(i, j, PIXEL_WHITE);
						}
					}
				}
			}
		}
		
		return outImg;
	}
	
	/**
	 * <p>
	 * Image binarization using Niblack's method outlined in "An Introduction to
	 * Digital Image Processing" in 1986
	 * </p>
	 * 
	 * @param inImg Image matrix to threshold
	 * @param k Percentage of pixels belonging to either the foreground, if <code>k</code>
	 * is positive, or to the background, if <code>k</code> is negative.
	 * @return Binarized image in an 8-bit unsigned matrix
	 */
	
	public static Mat niblack(Mat inImg, double k) {
		int rowStart, rowEnd;
		int colStart, colEnd;
		double centerVal;
		double localThreshold;
		
		Mat outImg;
		Mat imgWin;
		MatOfDouble meanMat, sdMat;
		
		outImg = new Mat(inImg.size(), CvType.CV_8UC1);
		meanMat = new MatOfDouble();
		sdMat = new MatOfDouble();
		
		if(inImg != null) {
			for(int i = 0; i < inImg.rows(); i++) {
				for(int j = 0; j < inImg.cols(); j++) {
					rowStart = i - 7;
					rowEnd = i + 7;
					colStart = j - 7;
					colEnd = j + 7;
					
					// Truncate window if it goes beyond the image
					if(rowStart < 0) {
						rowStart = 0;
					}
					
					if(rowEnd >= inImg.rows()) {
						rowEnd = inImg.rows() - 1;
					}
					
					if(colStart < 0) {
						colStart = 0;
					}
					
					if(colEnd >= inImg.cols()) {
						colEnd = inImg.cols() - 1;
					}
					
					// Get window. The default size is 15.
					imgWin = inImg.submat(rowStart, rowEnd, colStart, colEnd);
					centerVal = inImg.get(i, j)[0];
					
					// Get mean and SD of window
					Core.meanStdDev(imgWin, meanMat, sdMat);
					
					// Since there is only one channel, we get the first element.
					localThreshold = meanMat.get(0, 0)[0] + k * sdMat.get(0, 0)[0] * Math.sqrt(5 / 4);
					localThreshold = 128;
					
					if((int) (100 * (centerVal - localThreshold)) < 0) {
						outImg.put(i, j, PIXEL_BLACK);
					}
					else {
						outImg.put(i, j, PIXEL_WHITE);
					}
				}
			}
		}
		
		return outImg;
	}
	
}
